package de.rts.components;

import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D.Float;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import de.rts.logic.SwitchStatus;
import de.rts.logic.TrainScheduler;
import de.rts.ui.RailwayMapPanel;

public class Train extends Thread implements RailComponent
{
  private String                                              trainId;
  private String                                              name;
  private int                                                 velocity;
  private int                                                 deadline;
  private int                                                 priority;
  private int                                                 currentPosition, currentSensor, currentSignal,
      currentSwitch, currentRouteSwitch;
  private Polygon                                             switchPolygon;
  private static final int                                    UP              = 1, DOWN = 2;
  private int                                                 switchDirection = UP;
  private boolean                                             mutexSensor;
  private boolean                                             enteredSwitch;
  private boolean                                             arrived;
  private boolean                                             tempArrived;
  private Point                                               position;
  private Point                                               labelPosition;
  private Point                                               size;
  private LinkedList<Segment>                                 segments;
  private Map<Integer, LinkedHashMap<Integer, List<Segment>>> switchSegmentMap;
  private LinkedList<Sensor>                                  sensors;
  private LinkedList<Signal>                                  signals;
  private Segment                                             currentSwitchSegment;
  private TrainScheduler                                      scheduler;
  private SwitchStatus                                        switchStatus;
  private SwitchStatus                                        switchAccessStatus;

  public Train(String trainId)
  {
    this(trainId, new Point(), new Point());
  }

  public Train(String trainId, Point position, Point size)
  {
    this.trainId = trainId;
    this.position = position;
    this.size = size;
  }

  public Train(String trainId, String name, Point position, Point size, int velocity, int deadline)
  {
    super(name);
    this.trainId = trainId;
    this.name = name;
    this.position = position;
    this.size = size;
    this.velocity = velocity;
    this.deadline = deadline;
    this.segments = new LinkedList<Segment>();
    this.switchSegmentMap = new LinkedHashMap<Integer, LinkedHashMap<Integer, List<Segment>>>();
    this.sensors = new LinkedList<Sensor>();
    this.signals = new LinkedList<Signal>();
  }

  public int getVelocity()
  {
    return velocity;
  }

  public void setVelocity(int velocity)
  {
    this.velocity = velocity;
  }

  public int getDeadline()
  {
    return deadline;
  }

  public void setDeadline(int deadline)
  {
    this.deadline = deadline;
  }

  public String getTrainName()
  {
    return name;
  }

  public void setTrainName(String name)
  {
    this.name = name;
  }

  public int getTrainPriority()
  {
    return priority;
  }

  public void setTrainPriority(int priority)
  {
    this.priority = priority;
  }

  public Point getPosition()
  {
    return position;
  }

  public void setPosition(Point position)
  {
    this.position = position;
  }

  public Point getSize()
  {
    return size;
  }

  public void setSize(Point size)
  {
    this.size = size;
  }

  public String getTrainId()
  {
    return trainId;
  }

  public Segment getSegment(int index)
  {
    return this.segments.get(index);
  }

  public void setSegment(Segment segment)
  {
    this.segments.add(segment);
  }

  public LinkedHashMap<Integer, List<Segment>> getSwitchSegment(int index)
  {
    return this.switchSegmentMap.get(index);
  }

  public void setSwitchSegment(int key, LinkedHashMap<Integer, List<Segment>> units)
  {
    this.switchSegmentMap.put(key, units);
  }

  public Sensor getSensor(int index)
  {
    return this.sensors.get(index);
  }

  public void setSensor(Sensor sensor)
  {
    this.sensors.add(sensor);
  }

  public Signal getSignal(int index)
  {
    return this.signals.get(index);
  }

  public void setSignal(Signal signal)
  {
    this.signals.add(signal);
  }

  @Override
  public void run()
  {
    for (int i = 0; i < segments.size(); i++)
    {
      int startPosX = 0, endPosX = 0, routeDistance = 0;
      Segment segment = segments.get(i);
      Signal signal = signals.get(currentSignal);
      Sensor sensor1 = sensors.get(currentSensor++);
      Sensor sensor2 = sensors.get(currentSensor);
      Float trainFigure = RailwayMapPanel.getTrainFigure(trainId);
      Polygon sensorFigure = RailwayMapPanel.getSensorFigure(sensor1);

      if (currentSwitchSegment != null)
      {
        int startPosY = 0, endPosY = 0, distanceX = 0, distanceY = 0;
        Point[] sc = currentSwitchSegment.getSwitchCoordinates();
        switchPolygon = new Polygon(currentSwitchSegment.getSwitchXCoordinates(),
          currentSwitchSegment.getSwitchYCoordinates(), 4);
        Point start = sc[0];
        Point end = sc[sc.length - 1];
        if (start.y < end.y)
          switchDirection = DOWN;
        else
          switchDirection = UP;
        startPosX = start.x;
        endPosX = end.x;
        distanceX = endPosX - startPosX;
        startPosY = start.y;
        endPosY = end.y;
        distanceY = Math.abs(endPosY - startPosY);
        routeDistance = (distanceY > distanceX) ? distanceY : distanceX;
      }
      else
      {
        startPosX = sensor1.getXPositions()[0];
        endPosX = sensor2.getXPositions()[0];
        routeDistance = endPosX - startPosX;
      }

      while (currentPosition < routeDistance)
      {
        segment.setOccupied(true);
        if (enteredSwitch)
        {
          Point p = new Point(position.x, position.y);
          if (currentPosition > 5)
          {
            if (checkSwitchTrainIntersection(p, trainFigure.getBounds(), "+x")) changeXPosition("+", 1);
          }
          else
            changeXPosition("+", 1);
          if (switchDirection == DOWN)
          {
            if (currentPosition > 10)
            {
              if (checkSwitchTrainIntersection(p, trainFigure.getBounds(), "+y")) changeYPosition("+", 1);
            }
            else
              changeYPosition("+", 1);
          }
          else
          {
            if (currentPosition > 10)
            {
              if (checkSwitchTrainIntersection(p, trainFigure.getBounds(), "-y")) changeYPosition("-", 1);
            }
            else
              changeYPosition("-", 1);
          }

          if (currentPosition == routeDistance - 1)
          {
            if (switchDirection == DOWN && routeDistance > 60) changeYPosition("+", 6);
            if (switchDirection == UP && routeDistance > 60) changeYPosition("-", 6);
          }

        }
        else
          changeXPosition("+", 1);

        if (sensorFigure != null && sensorFigure.getBounds().intersects(trainFigure.getBounds()) && !mutexSensor)
        {
          switch (sensor1.getType())
          {
            case DEPARTURE:
              scheduler.notifyLog(name + " has departed\n");
              break;
            case SWITCH_START:
              enteredSwitch = true;
              leaveSingleTrack(segment);
              if (switchAccessStatus != SwitchStatus.OWNED) scheduler.enterSingleTrack(this, segment.getSegmentId());
              requestSwitchAccess(signal);
              currentSwitchSegment = null;
              break;
            case SWITCH_END:
              enteredSwitch = false;
              if (signal.getCurrentSignal() == RailComponent.GREEN) signal.setCurrentSignal(RailComponent.WHITE);
              if (currentSignal < signals.size() - 1) signal = signals.get(++currentSignal);
              int[] us = getUsedSwitches();
              switchStatus = scheduler.releaseSwitch(this, us[currentRouteSwitch % us.length]);
              switch (switchStatus)
              {
                case RELEASED:
                  currentRouteSwitch++;
                  if (currentSwitch < ROUTESWITCHES.get(Integer.parseInt(trainId)).length) currentSwitch++;
                  break;
              }
              break;
          }

          switch (sensor2.getType())
          {
            case SWITCH_START:
              requestSwitchAccess(signal);
              break;
            case SWITCH_END:
              break;
            case ARRIVAL:
              arrived = true;
              break;
          }
        }

        try
        {
          Thread.sleep(1000 / (velocity / 10));
        }
        catch (InterruptedException e)
        {
          interrupt();
        }
        catch (Exception e)
        {
        }
        currentPosition++;
        mutexSensor = true;
      }
      segment.setOccupied(false);
      mutexSensor = false;
      currentPosition = 0;
    }
    if (arrived)
    {
      notifyTimeline(arrived);
      scheduler.notifyLog(name + " has arrived\n");
      Thread.currentThread().interrupt();
    }
  }

  private void requestSwitchAccess(Signal signal)
  {
    int[] us = getUsedSwitches();
    switchAccessStatus = scheduler.requestSwitchAccess(this, us[currentRouteSwitch % us.length]);
    switch (switchAccessStatus)
    {
      case RESERVED:
        enableSwitch(signal);
        break;
      case OCCUPIED:
        signal.setCurrentSignal(RailComponent.RED);
        setCurrentSwitchSegment();
        break;
    }
  }

  private int[] getUsedSwitches()
  {
    return RailComponent.USED_SWITCHES.get(Integer.parseInt(trainId));
  }

  private void enableSwitch(Signal signal)
  {
    signal.setCurrentSignal(RailComponent.GREEN);
    List<Segment> switchSegmentUnit = getCurrentSwitchSegmentUnit();
    for (Segment s : switchSegmentUnit)
    {
      if (s.getSegmentId().equals(RailComponent.ROUTESWITCHES.get(Integer.parseInt(trainId))[currentSwitch]))
      {
        currentSwitchSegment = s;
        s.setSwitchEnabled(true);
      }
      else if (trainId.equals("3") && s.getSegmentId().equals(RailComponent.ROUTESWITCHES.get(1)[1]))
        s.setSwitchEnabled(true);
      else if (trainId.equals("1") && s.getSegmentId().equals(RailComponent.ROUTESWITCHES.get(3)[0]))
        s.setSwitchEnabled(true);
      else
        s.setSwitchEnabled(false);
    }
    mutexSensor = true;
  }

  private List<Segment> getCurrentSwitchSegmentUnit()
  {
    int id = Integer.parseInt(trainId);
    int[] us = RailComponent.USED_SWITCHES.get(id);
    return getSwitchSegment(id).get(us[currentRouteSwitch % us.length]);
  }

  private void setCurrentSwitchSegment()
  {
    List<Segment> switchSegmentUnit = getCurrentSwitchSegmentUnit();
    for (Segment s : switchSegmentUnit)
    {
      if (s.getSegmentId().equals(RailComponent.ROUTESWITCHES.get(Integer.parseInt(trainId))[currentSwitch]))
        currentSwitchSegment = s;
    }
  }

  private void leaveSingleTrack(Segment segment)
  {
    String[] strings = RailComponent.SINGLE_TRACKS.get(Integer.parseInt(trainId));
    for (String str : strings)
    {
      if (segment.getSegmentId().equals(str))
      {
        scheduler.leaveSingleTrack(this, segment.getSegmentId());
      }
    }
  }

  private void changeXPosition(String operation, int ticks)
  {
    if (operation.equals("+"))
    {
      position.x += ticks;
      labelPosition.x += ticks;
    }
    else if (operation.equals("-"))
    {
      position.x -= ticks;
      labelPosition.x -= ticks;
    }
  }

  private void changeYPosition(String operation, int ticks)
  {
    if (operation.equals("+"))
    {
      position.y += ticks;
      labelPosition.y += ticks;
    }
    else if (operation.equals("-"))
    {
      position.y -= ticks;
      labelPosition.y -= ticks;
    }
  }

  private boolean checkSwitchTrainIntersection(Point p, Rectangle r, String operation)
  {
    if (switchPolygon == null) return false;
    if (operation.equals("+x"))
      return switchPolygon.intersects(p.x += 1, p.y, r.getWidth(), r.getHeight());
    else if (operation.equals("+y"))
      return switchPolygon.intersects(p.x, p.y += 1, r.getWidth(), r.getHeight());
    else if (operation.equals("-y")) return switchPolygon.intersects(p.x, p.y -= 1, r.getWidth(), r.getHeight());
    return false;
  }

  @Override
  public String toString()
  {
    return name + " [priority: " + priority + ", deadline: " + deadline + "]";
  }

  public Point getLabelPosition()
  {
    return labelPosition;
  }

  public void setLabelPosition(Point labelPosition)
  {
    this.labelPosition = labelPosition;
  }

  public TrainScheduler getScheduler()
  {
    return scheduler;
  }

  public void setScheduler(TrainScheduler scheduler)
  {
    this.scheduler = scheduler;
  }

  public void notifyTimeline(boolean n)
  {
    boolean o = this.tempArrived;
    this.tempArrived = n;
    arrivedMessage.firePropertyChange("message" + trainId, o, n);
  }

  private PropertyChangeSupport arrivedMessage = new PropertyChangeSupport(this);

  public void addPropertyChangeListener(PropertyChangeListener l)
  {
    arrivedMessage.addPropertyChangeListener(l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l)
  {
    arrivedMessage.removePropertyChangeListener(l);
  }

  public boolean hasListener()
  {
    return arrivedMessage.hasListeners("message");
  }
}
